Skip to content

Prevent ObjectDisposedException from becoming unobserved during circuit cleanup #62662

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

ilonatommy
Copy link
Member

Issue similar to #62554.

log

[xUnit.net 00:20:37.86]     Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests.RedirectionTest.NavigationException_InAsyncContext_DoesNotBecomeUnobservedTaskException [FAIL]
  Failed Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests.RedirectionTest.NavigationException_InAsyncContext_DoesNotBecomeUnobservedTaskException [36 s]
  Error Message:
   OpenQA.Selenium.BrowserAssertFailedException : Xunit.Sdk.EqualException: Assert.Equal() Failure: Values differ
Expected: 0
Actual:   4

<h1>Hello, world!</h1><p id="unobserved-exceptions-count">4</p><h2>Unobserved Exceptions (for debugging):</h2>
        <ul><li>System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. (Cannot access a disposed object.
Object name: 'IServiceProvider'.)
 ---&gt; System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'IServiceProvider'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ThrowHelper.ThrowObjectDisposedException()
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.AspNetCore.Components.Server.Circuits.CircuitPersistenceManager.PauseCircuitAsync(CircuitHost circuit, Boolean saveStateToClient, CancellationToken cancellation) in /home/vsts/work/1/s/src/Components/Server/src/Circuits/CircuitPersistenceManager.cs:line 25
   at Microsoft.AspNetCore.Components.Server.Circuits.CircuitRegistry.PauseAndDisposeCircuitHost(CircuitHost circuitHost, Boolean saveStateToClient) in /home/vsts/work/1/s/src/Components/Server/src/Circuits/CircuitRegistry.cs:line 312
   --- End of inner exception stack trace ---</li><li>System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. (Cannot access a disposed object.
Object name: 'IServiceProvider'.)
 ---&gt; System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'IServiceProvider'.

Description

PeristanceManager.PauseCircuitAsync tries to resolve the ComponentStatePersistenceManager service from an already disposed service provider during circuit cleanup (triggered by garbage collection), causing an ObjectDisposedException to be thrown.

var persistenceManager = circuit.Services.GetRequiredService<ComponentStatePersistenceManager>();

Theoretically we are subscribed to circuitHost.UnhandledException but it did not get triggered in this situation. The fix is to observe the exception. I chose CircuitRegistry for it because it has CircuitHost_UnhandledException method to handle that gracefully.

This failure was obsered once on CI, see the log above. Running that test locally on repeat did not reveal any hits after 1k runs.

@ilonatommy ilonatommy added this to the 10.0-preview7 milestone Jul 10, 2025
@ilonatommy ilonatommy requested a review from javiercn July 10, 2025 15:13
@ilonatommy ilonatommy self-assigned this Jul 10, 2025
@ilonatommy ilonatommy requested a review from a team as a code owner July 10, 2025 15:13
@ilonatommy ilonatommy added the area-blazor Includes: Blazor, Razor Components label Jul 10, 2025
}
catch (ObjectDisposedException ex)
{
// Expected when service provider is disposed during circuit cleanup e.g. by forced GC
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this bit super well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test that was failing is forcing GC.


It looks like after that operation (that cleaned DI services), we are trying to do:
var persistenceManager = circuit.Services.GetRequiredService<ComponentStatePersistenceManager>();

Which is throwing. I'm not sure how else we can get to such situations, if we don't force GC - which is not a common pattern. However, I don't see any harm letting the exception be observed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to track the root cause of the objectdisposedexception, as it might be pointing out a real issue. If you are able to reproduce it, can you capture the callstack for the object disposed exception?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot reproduce it. I really tried, 1k runs locally and no failure

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are able to reproduce it, can you capture the callstack for the object disposed exception

We could add more logging of inner exception in the test and count on it to fail again on CI. However, we don't know if it will be on a PR that someone will report the failure. Or will they just hit rerun and be happy that it passed the next time.

@ilonatommy
Copy link
Member Author

The logging added in the previous commit is not that helpful - it's mostly duplication of current call stack printed in the log. I'll create an issue and we will observe the CI. We should come back to this PR to investigate f we are not calling dispose multiple times and why the exception happens.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants